/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
    Stmt,
    ArkMethod,
    Local, FunctionType,
} from "arkanalyzer";
import Logger, {LOG_MODULE_TYPE} from 'arkanalyzer/lib/utils/logger';
import {BaseChecker, BaseMetaData} from "../BaseChecker";
import {ClassMatcher, MatcherCallback, MatcherTypes, MethodMatcher} from "../../matcher/Matchers";
import {Defects, IssueReport} from "../../model/Defects";
import {CheckerUtils} from "../../utils/checker/CheckerUtils";
import {Rule} from "../../model/Rule";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoUnsafeArgumentCheck');
const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: "docs/no-unsafe-argument-check.md", // TODO: support url
    description: "Disallow calling a function with a value with type `any`"
};

export class NoUnsafeArgumentCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private clsMatcher: ClassMatcher = {
        matcherType: MatcherTypes.CLASS
    };

    private buildMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
        class: [this.clsMatcher],

    };

    public registerMatchers(): MatcherCallback[] {
        const matchBuildTs: MatcherCallback = {
            matcher: this.buildMatcher,
            callback: this.check
        }
        return [matchBuildTs];
    }

    public check = (targetMethod: ArkMethod) => {
        const severity = this.rule.alert ?? this.metaData.severity;
        const stmts = targetMethod.getBody()?.getCfg().getStmts() ?? [];
        if (!this.getFileExtension(targetMethod.getDeclaringArkClass().getDeclaringArkFile().getName(), 'ts')) {
            return;
        }
        for (const stmt of stmts) {
            const invokeExpr = CheckerUtils.getInvokeExprFromStmt(stmt);
            if (!invokeExpr) {
                continue;
            }
            for (let idx in invokeExpr!.getArgs()) {
                let local = invokeExpr!.getArgs()[idx];
                if (local.getType() instanceof FunctionType) {
                    break;
                }
                let localType = local.getType().getTypeString();
                if ((local as Local).getName) {
                    let name = (local as Local).getName() || '';
                    let isExtend: boolean = name.startsWith('...');
                    if (isExtend) {
                        name = name.slice(3);
                        let method: ArkMethod = targetMethod.getDeclaringArkClass().getDeclaringArkFile().getClasses()[0].getMethods()[0];
                        let bodyLocal = method.getBody()?.getLocals().get(name);
                        if (bodyLocal?.getType().getTypeString().includes("any")) {
                            localType = bodyLocal?.getType().getTypeString();
                        }
                    }
                }
                if (localType.includes("any")) {
                    if (invokeExpr.getMethodSignature().getMethodSubSignature().getParameterTypes()[idx]) {
                        if (invokeExpr.getMethodSignature().getMethodSubSignature().getParameterTypes()[idx].getTypeString() !== localType) {
                            this.addIssueReport(stmt, Number(idx), severity);
                        }
                    }
                }
            }
        }
    }

    private getFileExtension(filePath: string, filetype: string): boolean {
        const match = filePath.match(/\.([0-9a-zA-Z]+)$/);
        if (match) {
            const extension = match[1];
            return extension === filetype;
        }
        return false;
    }

    private addIssueReport(stmt: Stmt, index: number, severity: number) {
        const warnInfo = this.getLineAndColumn(stmt, index);
        let defect = new Defects(warnInfo.line, warnInfo.startCol, warnInfo.endCol, this.metaData.description, severity, this.rule.ruleId,
            warnInfo.filePath, this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defect, undefined));
    }

    private getLineAndColumn(stmt: Stmt, index: number) {
        const originalPositions = stmt.getOperandOriginalPositions();
        if (originalPositions![index]) {
            const line = originalPositions![index].getFirstLine();
            const arkFile = stmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile();
            if (arkFile) {
                let startCol = originalPositions![index].getFirstCol();
                const endCol = originalPositions![index].getLastCol();
                const filePath = stmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile().getFilePath();
                return {line, startCol, endCol, filePath: filePath}
            } else {
                logger.debug('originStmt or arkFile is null');
            }
        }
        return {line: -1, startCol: -1, endCol: -1, filePath: ''};
    }
}